(单)链表的基本操作及图形化解释(手绘)
Get the knowledge flowing and circulating! :)
目录
标题:(单)链表的基本操作Demo | 整体代码展示(可直接运行)→ 分模块代码解释 - 头部→ 分模块代码解释 - 单链表结点类型→ 分模块代码解释 - 单链表的初始化→ 分模块代码解释 - 单链表求长度→ 分模块代码解释 - 单链表判空→ 分模块代码解释 - 单链表插入操作:在第i个元素前插入→ 分模块代码解释 - 单链表取第i个元素→ 分模块代码解释 - 单链表元素遍历→ 分模块代码解释 - 查找元素e的位置→ 分模块代码解释 - 单链表删除指定位置上的元素→ 分模块代码解释 - 单链表的清空→ 分模块代码解释 - 单链表的销毁→ 分模块代码解释 - 一个关于元素插入的实例:在有序单链表中插入元素重点强调
xxxxxxxxxx2961// 单链表的相关操作 2
345
67
8// 下面定义的各个符号常量可以用来作为有些函数的返回状态码91011121314
15typedef int Status; // 函数返回状态类型16
17typedef int ElemType; // 链表元素类型18
19// 定义单链表结点类型20typedef struct LNode 21{22 ElemType data;23 struct LNode *next; // 指向后继结点24}ListNode, *LinkList;25
26LinkList InitList() // 初始化一带头结点的链表并返回头结点的指针27{28 LinkList L;29 30 L = (LinkList)malloc(sizeof(ListNode)); // 创建头结点31 L->next = NULL;32 33 return L;34}35
36int ListLength(LinkList L) // 求链表L的表长37{38 int n = 0;39 40 LinkList p;41 p = L->next;42 43 while (p != NULL)44 {45 n++;46 p = p->next;47 }48 return n;49}50
51Status ListEmpty(LinkList L) // 判断链表L是否为空,为空返回TRUE,否则返回FASLE52{53 if(L->next == NULL)54 return TRUE;55 else56 return FALSE;57}58
59void ListInsert(LinkList L, int i, ElemType e) // 在链表L的第i个元素前插入一个值为e的元素60{61 LinkList p = L;62 LinkList s;63 64 int j = 0;65 66 // 寻找第i个节点67 while(p && j < i - 1)68 {69 p = p->next;70 j++;71 }72 73 if(!p || j > i - 1) // i > len + 1 或 i < 174 {75 printf("插入位置不合理\n");76 exit(0);77 }78 79 s = (LinkList)malloc(sizeof(ListNode));80 s->data = e;81 s->next = p->next;82 p->next = s;83}84
85ElemType GetElem(LinkList L, int i) // 取链表L中的第i个元素86{87 ElemType e;88 89 LinkList p = L->next; // p指向第一个元素90 91 int j = 1;92 while(p && j < i)93 {94 p = p->next;95 ++j;96 }97 98 if(!p || j > i)99 return ERROR;100 101 e = p->data;102 return e;103}104
105void ListTravers(LinkList L) // 输出链表L的所有元素106{107 LinkList p = L->next;108 109 if(p == NULL)110 printf("表为空");111 else112 {113 while(p != NULL)114 {115 printf("%d ", p->data);116 p = p->next;117 }118 }119 printf("\n");120}121
122int LocateElem(LinkList L, ElemType e) // 在链表L中定位值为e的元素位置123{124 LinkList p = L->next;125 126 int i = 1;127 128 while(p && p->data != e)129 { 130 p = p->next;131 i++;132 }133 134 if(p == NULL) 135 return ERROR;136
137 return i;138}139
140void ListDelete(LinkList L, int i) // 删除链表L的第i个元素141{142 LinkList p = L;143 LinkList q;144 145 int j = 0;146 while(p->next && j < i - 1) // 定位p指向要删除元素的前一个节点147 {148 p = p->next;149 j++;150 }151 152 if(p->next == NULL || j > i - 1) // 删除位置不合理153 return;154 155 q = p->next;156 p->next = q->next;157 free(q);158}159
160void ClearList(LinkList L) // 重置链表L为空表161{162 LinkList p = L->next;163 LinkList q = p->next;164 165 L->next = NULL;166 167 while(q != NULL)168 {169 free(p);170 p = q;171 q = p->next;172 }173 free(p);174}175
176void DestroyList(LinkList L) // 销毁链表L177{178 LinkList p = L;179 LinkList q = p->next;180 while(q != NULL)181 {182 free(p);183 p = q;184 q = p->next;185 }186 free(p);187}188
189void Insert(LinkList L, ElemType x) // 在非递减的有序表L中插入值为x的元素,插入后仍然保持是有序性190{191 LinkList p = L->next;192 LinkList q = L;193 LinkList s;194 195 while(p && p->data < x) // p指向要插入位置的后一个位置,q指向前一个元素196 {197 q = p;198 p = p->next;199 }200 201 if(p == NULL) // 表示要插在最后202 {203 s = (LinkList)malloc(sizeof(ListNode));204 s->data = x;205 q->next = s;206 p = s;207 p->next = NULL;208 }209 else210 {211 s = (LinkList)malloc(sizeof(ListNode));212 s->data = x;213 q->next = s;214 s->next = p;215 }216}217
218int main()219{220 LinkList list1;221 LinkList list2;222 223 ElemType x;224 int i;225
226 // 定义过InitList函数和ListLength函数后可以执行如下两句测试227 list1 = InitList();228 printf("list1.length = %d.\n", ListLength(list1)); // list1.length = 0229
230 // 定义过ListEmpty函数后可以执行如下if语句测试231 if(ListEmpty(list1)) // list1为空表232 printf("\nlist1为空表\n");233 else234 printf("\nlist1为非空表\n");235 236 // 定义过ListInsert函数后可以执行如下三条测试237 printf("\n请按顺序输入10到100, 共10个整数创建链表:\n");238 for(i = 1; i <= 10; i++) // 测试时依次输入10,20,30,40,50,60,70,80,90,100239 {240 scanf("%d", &x);241 ListInsert(list1, i, x);242 }243 printf("\nlist1.length = %d.\n", ListLength(list1)); // list1.length = 10244
245 // 定义过GetElem函数后可以执行如下一条句测试246 printf("\nlist1的第4个元素为%d.\n", GetElem(list1, 4)); // list1的第4个元素为40247
248 // 定义过ListTravers函数后可以执行如下两句测试249 printf("\n创建后list1表的内容为:\n");250 ListTravers(list1); // 10 20 30 40 50 60 70 80 90 100251 252 // 定义过LocateElem函数后可以执行如下一条句测试253 printf("\n值为50的元素在list1表中的位序为%d.\n", LocateElem(list1, 50)); // 值为50的元素在list1表中的位序为5254
255 // 定义过ListDelete函数后可以执行如下三条句测试256 ListDelete(list1, 4);257 printf("\n删除第4个元素后:\n");258 ListTravers(list1); // 10 20 30 50 60 70 80 90 100259
260 // 定义过ListInsert函数和ListTravers函数后可以执行如下三条句测试261 ListInsert(list1, 4, 44);262 printf("\n在第4个元素前插入44后:\n");263 ListTravers(list1); // 10 20 30 44 50 60 70 80 90 100264
265 // 定义过ClearList函数后可以执行如下两条句测试266 ClearList(list1);267 printf("\nlist1.length = %d.\n", ListLength(list1)); // list1.length=0268
269 // 定义过DestroyList函数后可以执行如下语句测试 270 printf("list1.next = %d.\n", list1->next); // list1.next = 0. 271 DestroyList(list1);272 printf("list1.next = %d.\n", list1->next); // list1.next = 随机数.273
274 // 创建第二个非递减有序表275 list2 = InitList(); 276 printf("\n请按顺序输入5个整数12,14,16,18,18创建非递减有序表:\n");277 for(i = 1; i <= 5; i++) // 测试时依次输入12,14,16,18,18278 {279 scanf("%d", &x);280 ListInsert(list2, i, x);281 }282
283 // 定义过Insert函数后可以执行如下语句测试284 Insert(list2, 17);285 printf("\n插入17之后的list1表的内容为:\n");286 ListTravers(list2); // 12,14,16,17,18,18287 288 return 0;289}290
291/*292测试样例:293 29410 20 30 40 50 60 70 80 90 10029512 14 16 17 18 18296*/头部xxxxxxxxxx181// 头部2// 链表的相关操作 3
456
78
9// 下面定义的各个符号常量可以用来作为有些函数的返回状态码101112131415
16typedef int Status; // 函数返回状态类型17
18typedef int ElemType; // 链表元素类型还记得[Coding004]吗?typedef v.s #define

单链表结点类型xxxxxxxxxx61// 定义单链表结点类型2typedef struct LNode 3{4 ElemType data;5 struct LNode *next; // 指向后继结点6}ListNode, *LinkList;学习的方法之一:类别。把不熟悉的类比到熟悉的知识上。(如果有错误,欢迎指正哦~ :P)

单链表的初始化xxxxxxxxxx91LinkList InitList() // 初始化一带头结点的链表并返回头结点的指针2{3 LinkList L;4 5 L = (LinkList)malloc(sizeof(ListNode)); // 创建头结点6 L->next = NULL;7 8 return L;9}注意谁是指针(LinkList),谁不是指针(ListNode).

单链表求长度xxxxxxxxxx141int ListLength(LinkList L) // 求链表L的表长2{3 int n = 0;4 5 LinkList p;6 p = L->next;7 8 while (p != NULL)9 {10 n++;11 p = p->next;12 }13 return n;14}什么是空的链表?
就是不含有实际意义值的链表:只有一个dummy结点的链表,后面没有链上任何东西!

单链表判空xxxxxxxxxx71Status ListEmpty(LinkList L) // 判断链表L是否为空,为空返回TRUE,否则返回FASLE2{3 if(L->next == NULL)4 return TRUE;5 else6 return FALSE;7}空链表!只有dummy伪结点的链表。

单链表插入操作:在第i个元素前插入xxxxxxxxxx251void ListInsert(LinkList L, int i, ElemType e) // 在链表L的第i个元素前插入一个值为e的元素2{3 LinkList p = L;4 LinkList s;5 6 int j = 0;7 8 // 寻找第i个节点9 while(p && j < i - 1)10 {11 p = p->next;12 j++;13 }14 15 if(!p || j > i - 1) // i > len + 1 或 i < 116 {17 printf("插入位置不合理\n");18 exit(0);19 }20 21 s = (LinkList)malloc(sizeof(ListNode));22 s->data = e;23 s->next = p->next;24 p->next = s;25}在链表中插入/删除一个元素的重要前提:先找到待操作的元素的前一个位置,不然链表会断掉!

单链表取第i个元素xxxxxxxxxx191ElemType GetElem(LinkList L, int i) // 取链表L中的第i个元素2{3 ElemType e;4 5 LinkList p = L->next; // p指向第一个元素6 7 int j = 1;8 while(p && j < i)9 {10 p = p->next;11 ++j;12 }13 14 if(!p || j > i)15 return ERROR;16 17 e = p->data;18 return e;19}如果搞不清下标怎么操作的,就自己举个demo,画一画!

单链表元素遍历xxxxxxxxxx161void ListTravers(LinkList L) // 输出链表L的所有元素2{3 LinkList p = L->next;4 5 if(p == NULL)6 printf("表为空");7 else8 {9 while(p != NULL)10 {11 printf("%d ", p->data);12 p = p->next;13 }14 }15 printf("\n");16}链表的经典移动操作:
p = p->next;

查找元素e的位置xxxxxxxxxx171int LocateElem(LinkList L, ElemType e) // 在链表L中定位值为e的元素位置2{3 LinkList p = L->next;4 5 int i = 1;6 7 while(p && p->data != e)8 { 9 p = p->next;10 i++;11 }12 13 if(p == NULL) 14 return ERROR;15
16 return i;17}切记:p是指针。可以理解为一个值,也可以理解为一个块。这里用橙色方框表示,是不是看起来更清晰了!

单链表删除指定位置上的元素xxxxxxxxxx191void ListDelete(LinkList L, int i) // 删除链表L的第i个元素2{3 LinkList p = L;4 LinkList q;5 6 int j = 0;7 while(p->next && j < i - 1) // 定位p指向要删除元素的前一个节点8 {9 p = p->next;10 j++;11 }12 13 if(p->next == NULL || j > i - 1) // 删除位置不合理14 return;15 16 q = p->next;17 p->next = q->next;18 free(q);19}在链表中插入/删除一个元素的重要前提:先找到待操作的元素的前一个位置,不然链表会断掉!

单链表的清空xxxxxxxxxx151void ClearList(LinkList L) // 重置链表L为空表2{3 LinkList p = L->next;4 LinkList q = p->next;5 6 L->next = NULL;7 8 while(q != NULL)9 {10 free(p);11 p = q;12 q = p->next;13 }14 free(p);15}单链表的销毁xxxxxxxxxx121void DestroyList(LinkList L) // 销毁链表L2{3 LinkList p = L;4 LinkList q = p->next;5 while(q != NULL)6 {7 free(p);8 p = q;9 q = p->next;10 }11 free(p);12}概念明晰:
清空:保留头部的dummy伪结点;
销毁:全部都free掉!

一个关于元素插入的实例:在有序单链表中插入元素xxxxxxxxxx281void Insert(LinkList L, ElemType x) // 在非递减的有序表L中插入值为x的元素,插入后仍然保持是有序性2{3 LinkList p = L->next;4 LinkList q = L;5 LinkList s;6 7 while(p && p->data < x) // p指向要插入位置的后一个位置,q指向前一个元素8 {9 q = p;10 p = p->next;11 }12 13 if(p == NULL) // 表示要插在最后14 {15 s = (LinkList)malloc(sizeof(ListNode));16 s->data = x;17 q->next = s;18 p = s;19 p->next = NULL;20 }21 else22 {23 s = (LinkList)malloc(sizeof(ListNode));24 s->data = x;25 q->next = s;26 s->next = p;27 }28}重点操作:找到前一个元素!

这里我整理学习的是C语言版的单链表实现。当然还有复杂的链表,比如双向链表。后面不一定会整理,但是这个单链表一定是重中之重!
重中之重的重中之重:在编程的过程要有意识地注意找到待操作元素的前一个元素的位置。
后面会遇到复杂的代码操作,可能会有很多指针,
p,q,r,s...等等。要注意辨析每个指针的作用!